package lxs.swift.collector.sync;

import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public abstract class Pool<T> {
    private final Mutex<Content<T>> ctx = new Mutex<>(new Content<>());
    private final Condition waitFree = ctx.condition();

    public abstract <R> R get(@NotNull Function<T, R> f);

    public abstract <R> R tryGet(long wait, @NotNull TimeUnit unit, @NotNull Function<T, R> f);

    public void use(@NotNull Consumer<T> f) {
        Objects.requireNonNull(f);
        this.get(r -> {
            f.accept(r);
            return null;
        });
    }

    public void tryUse(long wait, @NotNull TimeUnit unit, @NotNull Consumer<T> f) {
        Objects.requireNonNull(unit);
        Objects.requireNonNull(f);
        this.tryGet(wait, unit, r -> {
            f.accept(r);
            return null;
        });
    }

    /**
     * 工厂式对象池，当需要时直接生产一个对象，当对象长时间空闲时清除
     *
     * @param constructor  对象提供器
     * @param capacity     最大容量
     * @param freeTime     空闲时间
     * @param freeTimeUnit 空闲单位
     * @param pollingTime  轮询检查空闲时间 单位毫秒
     */
    public static <T> Pool<T> factory(Supplier<T> constructor, int capacity, int freeTime, TimeUnit freeTimeUnit, long pollingTime) {
        return new FactoryPool<>(constructor, capacity, freeTime, freeTimeUnit, pollingTime);
    }


    @SafeVarargs
    public static <T> Pool<T> fixed(@NotNull T... initialValues) {
        return new FixedPool<>(Arrays.asList(initialValues));
    }

    public static <T> Pool<T> fixed(Collection<T> initialValues) {
        return new FixedPool<>(initialValues);
    }

    private static class FactoryPool<T> extends Pool<T> {
        private final Supplier<T> constructor;
        private final int capacity;
        private final int freeTime;
        private final TimeUnit freeTimeUnit;

        public FactoryPool(Supplier<T> constructor, int capacity, int freeTime, TimeUnit freeTimeUnit, long pollingTime) {
            Objects.requireNonNull(constructor);
            if (capacity <= 0) throw new IllegalArgumentException("capacity must greater than 0");
            this.constructor = constructor;
            this.capacity = capacity;
            this.freeTime = freeTime;
            this.freeTimeUnit = freeTimeUnit;
            //循环清理超出空闲时间的对象
            if (freeTime > 0) {
                Thread thread = new Thread(() -> {
                    while (pollingTime > 0) {
                        super.ctx.use((ctx -> {
                            Iterator<Map.Entry<Long, Wrap<T>>> iterator = ctx.idMapValue.entrySet().iterator();
                            while (iterator.hasNext()) {
                                Map.Entry<Long, Wrap<T>> next = iterator.next();
                                Wrap<T> value = next.getValue();
                                if (freeTimeout(value.lastUse) && value.isFree) {
                                    iterator.remove();
                                }
                            }
                        }));
                        try {
                            Thread.sleep(pollingTime);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                });
                thread.start();
            }
        }

        private boolean freeTimeout(long lastUse) {
            if (freeTime < 0) return false;
            long ms = freeTimeUnit.convert(freeTime, TimeUnit.MILLISECONDS);
            return System.currentTimeMillis() - lastUse >= ms;
        }

        @Override
        public <R> R get(@NotNull Function<T, R> f) {
            Objects.requireNonNull(f);
            Wrap<T> wrap = super.ctx.map(ctx -> {
                Wrap<T> freeOne = ctx.getFreeOne();
                if (freeOne != null) {
                    return freeOne;
                }
                //容量满了
                while (ctx.idMapValue.size() >= this.capacity) {
                    try {
                        super.waitFree.await();
                        freeOne = ctx.getFreeOne();
                        if (freeOne != null) {
                            return freeOne;
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                T newValue = constructor.get();
                return ctx.newOne(newValue);
            });
            try {
                T value = wrap.getValue();
                return f.apply(value);
            } finally {
                super.ctx.use(ctx -> {
                    ctx.free(wrap);
                    super.waitFree.signal();
                });
            }
        }

        @Override
        public <R> R tryGet(long wait, @NotNull TimeUnit unit, @NotNull Function<T, R> f) {
            Objects.requireNonNull(unit);
            Objects.requireNonNull(f);
            Wrap<T> wrap = super.ctx.map(ctx -> {
                Wrap<T> freeOne = ctx.getFreeOne();
                if (freeOne != null) {
                    return freeOne;
                }
                //容量满了
                while (ctx.idMapValue.size() >= this.capacity) {
                    try {
                        super.waitFree.await(wait, unit);
                        freeOne = ctx.getFreeOne();
                        if (freeOne != null) {
                            return freeOne;
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                T newValue = constructor.get();
                return ctx.newOne(newValue);
            });
            try {
                T value = wrap.getValue();
                return f.apply(value);
            } finally {
                super.ctx.use(ctx -> {
                    ctx.free(wrap);
                    super.waitFree.signal();
                });
            }
        }
    }

    private static class FixedPool<T> extends Pool<T> {
        public FixedPool(Collection<T> initialValues) {
            if (initialValues.size() <= 1) throw new IllegalArgumentException("initialValues size must greater than 1");
            Objects.requireNonNull(initialValues);
            super.ctx.use((ctx) -> {
                for (T initialValue : initialValues) {
                    ctx.newOne(initialValue);
                }
            });
        }


        @Override
        public <R> R get(@NotNull Function<T, R> f) {
            Wrap<T> wrap = super.ctx.map(ctx -> {
                Wrap<T> freeOne = ctx.getFreeOne();
                if (freeOne != null) {
                    return freeOne;
                }
                try {
                    super.waitFree.await();
                    freeOne = ctx.getFreeOne();
                    if (freeOne != null) {
                        return freeOne;
                    }
                    throw new RuntimeException("get value failed: there are no free objects");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            try {
                T value = wrap.getValue();
                return f.apply(value);
            } finally {
                super.ctx.use(ctx -> {
                    ctx.free(wrap);
                    super.waitFree.signal();
                });
            }
        }

        @Override
        public <R> R tryGet(long wait, @NotNull TimeUnit unit, @NotNull Function<T, R> f) {
            Wrap<T> wrap = super.ctx.map(ctx -> {
                Wrap<T> freeOne = ctx.getFreeOne();
                if (freeOne != null) {
                    return freeOne;
                }
                try {
                    super.waitFree.await(wait, unit);
                    freeOne = ctx.getFreeOne();
                    if (freeOne != null) {
                        return freeOne;
                    }
                    throw new RuntimeException("get value failed: there are no free objects");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            try {
                T value = wrap.getValue();
                return f.apply(value);
            } finally {
                super.ctx.use(ctx -> {
                    ctx.free(wrap);
                    super.waitFree.signal();
                });
            }
        }
    }

    private static class Content<T> {
        long curId = 1;
        final Map<Long, Wrap<T>> idMapValue = new HashMap<>();
        final Set<Long> freeIds = new HashSet<>();

        private Wrap<T> newOne(T value) {
            Wrap<T> wrap = new Wrap<>(value);
            wrap.id = curId++;
            idMapValue.put(wrap.id, wrap);
            freeIds.add(wrap.id);
            wrap.isFree = false;
            return wrap;
        }

        private Wrap<T> getFreeOne() {
            Iterator<Long> iterator = freeIds.iterator();
            while (iterator.hasNext()) {
                Long id = iterator.next();
                Wrap<T> wrap = idMapValue.get(id);
                iterator.remove();
                if (wrap == null) {
                    continue;
                }
                wrap.isFree = false;
                return wrap;
            }
            return null;
        }

        private void free(Wrap<T> wrap) {
            wrap.isFree = true;
            freeIds.add(wrap.id);
        }
    }

    private static class Wrap<T> {
        public long id;
        private long lastUse;
        private final T value;
        private boolean isFree;

        public Wrap(T value) {
            this.lastUse = System.currentTimeMillis();
            this.value = value;
        }

        public T getValue() {
            lastUse = System.currentTimeMillis();
            return value;
        }
    }
}
